package com.ruoyi.activiti.utils;

import com.ruoyi.common.utils.StringUtils;
import org.activiti.engine.*;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.history.HistoricActivityInstanceQuery;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.history.HistoricProcessInstanceQuery;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.DeploymentBuilder;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.repository.ProcessDefinitionQuery;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.IdentityLink;
import org.activiti.engine.task.Task;
import org.apache.commons.io.IOUtils;

import javax.validation.constraints.NotNull;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipInputStream;

public class ActivitiUtils {

    private static final ProcessEngine PROCESS_ENGINE = ProcessEngines.getDefaultProcessEngine();

    /**
     * 资源管理类
     */
    private static final RepositoryService repositoryService = PROCESS_ENGINE.getRepositoryService();

    /**
     * 运行时管理类
     */
    private static final RuntimeService runtimeService = PROCESS_ENGINE.getRuntimeService();

    /**
     * 任务管理类
     */
    private static final TaskService taskService = PROCESS_ENGINE.getTaskService();

    /**
     * 历史数据管理类
     */
    private static final HistoryService historyService = PROCESS_ENGINE.getHistoryService();

    /**
     * 流程部署--将bpmn文件放在resources/bpmn 文件夹中进行部署
     *
     * @param name        部署时候可以起一个名字
     * @param bpmnName    bpmn文件名[带文件后缀 .bpmn]
     * @param bpmnImgName bpmn图片名[带图片后缀]
     */
    public static Map<String, Object> importProcessByResources(
            @NotNull String name, @NotNull String bpmnName, String bpmnImgName) {
        DeploymentBuilder deployment = repositoryService.createDeployment();
        deployment.name(name);
        deployment.addClasspathResource("processes/" + bpmnName);
        if (bpmnImgName != null) {
            deployment.addClasspathResource("processes/" + bpmnImgName);
        }
        // 执行部署
        Deployment deploy = deployment.deploy();
        return getMap(deploy);
    }

    /**
     * 流程部署-- 通过zip包进行bpmn文件部署
     *
     * @param zipPath: zip文件的路径 带[.zip] 后缀
     * @param name:    给部署起一个名字
     */
    public static Map<String, Object> importProcessByZip(String zipPath, String name)
            throws FileNotFoundException {
        FileInputStream fileInputStream = new FileInputStream(zipPath);
        ZipInputStream stream = new ZipInputStream(fileInputStream);
        Deployment deploy =
                repositoryService
                        .createDeployment()
                        .addZipInputStream(stream)
                        .name(name)
                        .deploy();
        return getMap(deploy);
    }

    private static Map<String, Object> getMap(Deployment deploy) {
        Map<String, Object> map = new HashMap<>(4);
        // 部署id
        map.put("deploymentId", deploy.getId());
        // 部署名称
        map.put("deploymentName", deploy.getName());
        // 部署时间
        map.put("deploymentTime", deploy.getDeploymentTime());
        return map;
    }

    /**
     * 查询流程定义集合【流程定义模板列表】
     *
     * @param key bpmn文件中的 processId processId = null 则查全部模板
     */
    public static List<Map<String, Object>> queryProcessDefinition(String key) {
        ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery();
        if (key != null) {
            // 根据指定key查出模板
            query.processDefinitionKey(key);
        }
        List<Map<String, Object>> result = new ArrayList<>();
        List<ProcessDefinition> list = query.list();
        for (ProcessDefinition processDefinition : list) {
            Map<String, Object> map = new HashMap<>(8);
            // 流程部署id
            map.put("deploymentId", processDefinition.getDeploymentId());
            // 流程定义id
            map.put("processDefinitionId", processDefinition.getId());
            // 流程定义key
            map.put("processDefinitionKey", processDefinition.getKey());
            // 流程定义名称
            map.put("processDefinitionName", processDefinition.getName());
            // 流程定义版本
            map.put("processDefinitionVersion", processDefinition.getVersion());
            result.add(map);
        }
        return result;
    }

    /**
     * 删除流程部署信息
     *
     * @param deploymentId 流程部署id
     * @param cascade:     true-连同已经存在的运行任务一起删除 false-不删除运行的任务
     * @author zh
     */
    public static void deleteProcessDefinition(String deploymentId, boolean cascade) {
        repositoryService.deleteDeployment(deploymentId, cascade);
    }

    /**
     * 根据指定的bpmn的 processId 下载已经存在数据库中的文件 需要 common-io.jar
     *
     * @param key          bpmn文件中的 processId
     * @param imgSavePath  需要把对应的图片文件保存的绝对路径【例：D:/bpmn/test-image.png】
     * @param bpmnSavePath 需要把对应的bpmn文件保存的绝对路径【例：D:/bpmn/test-bpmn.bpmn】
     */
    public static void downDeployment(String key, String imgSavePath, String bpmnSavePath) {
        ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery();
        // 根据指定key查出模板
        query.processDefinitionKey(key);
        for (ProcessDefinition processDefinition : query.list()) {
            //    拿到deploymentId
            String deploymentId = processDefinition.getDeploymentId();
            // 图片目录和名称
            String pngName = processDefinition.getDiagramResourceName();
            InputStream resourceAsStream = repositoryService.getResourceAsStream(deploymentId, pngName);
            String bpmnName = processDefinition.getResourceName();
            InputStream bpmnNameResourceAsStream =
                    repositoryService.getResourceAsStream(deploymentId, bpmnName);
            File pngFile = new File(imgSavePath);
            File bpmnFile = new File(bpmnSavePath);
            FileOutputStream pngFileOutputStream = null;
            FileOutputStream bpmnFileOutputStream = null;
            try {
                pngFileOutputStream = new FileOutputStream(pngFile);
                bpmnFileOutputStream = new FileOutputStream(bpmnFile);
                IOUtils.copy(resourceAsStream, pngFileOutputStream);
                IOUtils.copy(bpmnNameResourceAsStream, bpmnFileOutputStream);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                // 关闭流输入流
                try {
                    resourceAsStream.close();
                    bpmnNameResourceAsStream.close();
                    assert pngFileOutputStream != null;
                    assert bpmnFileOutputStream != null;
                    // 关闭流输出流
                    pngFileOutputStream.close();
                    bpmnFileOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 开启一个流程实例
     *
     * @param processDefinitionId 流程定义id
     * @param businessKey         可以绑定一个自定义的业务id
     * @param params:             key:在bpmn文件中定义的变量占位符【例： ${userName}】 value:对应的占位值 【例： 小明】
     * @author zh
     */
    public static Map<String, Object> startProcessInstanceByKey(
            String processDefinitionId, String businessKey, Map<String, Object> params) {
        // 根据流程定义ID启动流程
        ProcessInstance one;
        if(StringUtils.isBlank(businessKey)){
            one = runtimeService.startProcessInstanceByKey(processDefinitionId, params);
        }else {
            one = runtimeService.startProcessInstanceByKey(processDefinitionId, businessKey, params);
        }
        Map<String, Object> map = new HashMap<>(4);
        // 流程定义id
        map.put("processDefinitionId", one.getProcessDefinitionId());
        // 流程实例id
        map.put("id", one.getId());
        // 当前活动id
        map.put("activityId", one.getActivityId());
        return map;
    }

    /**
     * 开启一个流程实例
     *
     * @param processDefinitionId 流程定义id
     * @param businessKey         可以绑定一个自定义的业务id
     * @author zh
     */
    public static Map<String, Object> startProcessInstanceByKey(
            String processDefinitionId, String businessKey) {
        // 根据流程定义ID启动流程
        ProcessInstance one;
        if(StringUtils.isBlank(businessKey)){
            one = runtimeService.startProcessInstanceByKey(processDefinitionId);
        }else {
            one = runtimeService.startProcessInstanceByKey(processDefinitionId, businessKey);
        }
        Map<String, Object> map = new HashMap<>(4);
        // 流程定义id
        map.put("processDefinitionId", one.getProcessDefinitionId());
        // 流程实例id
        map.put("id", one.getId());
        // 当前活动id
        map.put("activityId", one.getActivityId());
        return map;
    }

    /**
     * 【如果某个任务设置了负责人】则可通过该方法查询负责人【根据人名查出 自己有哪些任务待完成】
     *
     * @param key       bpmn里面的id值
     * @param assignee:负责人名
     * @author zh
     */
    public static List<Map<String, Object>> taskListByAssignee(String key, String assignee) {
        // 获取已经运行的任务列表
        List<Task> taskList = taskService.createTaskQuery().processDefinitionKey(key).taskAssignee(assignee).list();
        return getMaps(taskList);
    }

    /**
     * 【如果某个任务设置了候选人】则可通过该方法查询候选人【根据人名查出 自己有哪些任务待完成】
     *
     * @param key                bpmn里面的id值
     * @param candidateUserName: 候选人名
     * @author zh
     */
    public static List<Map<String, Object>> taskListByCandidateUser(String key, String candidateUserName) {
        // 获取 已经运行的任务列表
        List<Task> taskList = taskService.createTaskQuery().processDefinitionKey(key).taskCandidateUser(candidateUserName).list();
        return getMaps(taskList);
    }

    private static List<Map<String, Object>> getMaps(List<Task> taskList) {
        List<Map<String, Object>> result = new ArrayList<>();
        for (Task task : taskList) {
            Map<String, Object> map = new HashMap<>(8);
            // 流程实例id
            map.put("processInstanceId", task.getProcessInstanceId());
            // 任务id
            map.put("id", task.getId());
            // 任务负责人
            map.put("assignee", task.getAssignee());
            // 任务名称
            map.put("name", task.getName());
            // 任务创建时间
            map.put("createTime", task.getCreateTime());
            result.add(map);
        }
        return result;
    }

    /**
     *  添加候选人
     *
     *@param taskId 任务id
     *@param userId 候选人
     *@author zh
     */
    public static void addGroupUser(String taskId, String userId){
        taskService.addCandidateUser(taskId, userId);
    }

    /**
     *  删除候选人
     *
     *@param taskId 任务id
     *@param userId 候选人
     *@author zh
     */
    public static void deleteGroupUser(String taskId, String userId){
        taskService.deleteCandidateUser(taskId, userId);
    }

    /**
     * 【如果某个任务设置了候选人】 某个负责人进行拾取任务【通过任务id和人名 开始拾取任务】
     *
     * @param taskId：           任务id
     * @param candidateUserName 候选人名
     * @return true-拾取成功 false=拾取失败
     * @author zh
     */
    public static boolean claimTask(String taskId, String candidateUserName) {
        // 根据条件 查出任务
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        // 能查到任务并且 传入参数也符合候选人 才能进行拾取
        if (task != null && isCandidate(taskId, candidateUserName)) {
            // 进行拾取操作 其实就是把指定任务的assignee设置对应的负责人名
            taskService.claim(task.getId(), candidateUserName);
            return true;
        }
        return false;
    }

    /**
     * 判断指定任务的候选人名单中是否有： candidateUserName
     *
     * @return true:是候选人 false：不是候选人
     */
    private static boolean isCandidate(String taskId, String candidateUserName) {
        for (IdentityLink link : taskService.getIdentityLinksForTask(taskId)) {
            if (candidateUserName.equals(link.getUserId())) {
                return true;
            }
        }
        return false;
    }

    /**
     * [描述]【如果某个任务设置了候选人】 负责人已经拾取某个任务以后 打算再归还回去
     *
     * @param taskId：           任务id
     * @param candidateUserName 候选人名
     * @author zh
     */
    public static boolean claimTaskReturn(String taskId, String candidateUserName) {
        // 根据条件 查出任务
        Task task =
                taskService
                        .createTaskQuery()
                        .taskId(taskId)
                        .taskCandidateUser(candidateUserName)
                        .singleResult();
        if (task != null) {
            // 归还操作 其实就是把该任务的 assignee 设置成null
            taskService.setAssignee(task.getId(), null);
            return true;
        }
        return false;
    }

    /**
     * 【如果某个任务设置了候选人】某个任务虽然是自己的 但是可以交给其他人进行完成
     *
     * @param taskId：           任务id
     * @param candidateUserName 候选人名
     * @param assigneeName：     交接人
     * @author zh
     */
    public static boolean taskHandover(String taskId, String candidateUserName, String assigneeName) {
        // 根据条件 查出任务
        Task task =
                taskService
                        .createTaskQuery()
                        .taskId(taskId)
                        .taskCandidateUser(candidateUserName)
                        .singleResult();
        // 交接操作 这个任务是自己虽然是候选人 但是 可以直接把这个任务交接给别人进行完成
        if (task != null) {
            taskService.setAssignee(task.getId(), assigneeName);
            return true;
        }
        return false;
    }

    /**
     * 根据名字 查询个人待执行的任务【已经把任务拾取了】【带分页】
     *
     * @param taskAssignName 人名【例：班长、经理、总经理等...】
     * @param key            bpmn文件中的processId
     * @param firstResult    当前页【默认从0开始】
     * @param maxResults     页长
     */
    public static List<Map<String, Object>> taskListByMy(
            String taskAssignName, String key, Integer firstResult, Integer maxResults) {
        List<Task> list =
                taskService
                        .createTaskQuery()
                        .processDefinitionKey(key)
                        .taskAssignee(taskAssignName)
                        .listPage(firstResult, maxResults);
        List<Map<String, Object>> result = new ArrayList<>();
        for (Task task : list) {
            Map<String, Object> taskMap = new HashMap<>(8);
            // 任务id
            taskMap.put("taskId", task.getId());
            // 任务名称
            taskMap.put("taskName", task.getName());
            // 创建时间
            taskMap.put("createTime", task.getCreateTime());
            // 任务负责人
            taskMap.put("taskAssignee", task.getAssignee());
            // 流程实例id
            taskMap.put("processInstanceId", task.getProcessInstanceId());
            result.add(taskMap);
        }
        return result;
    }

    /**
     * 完成个人任务
     *
     * @param taskAssignName： 任务代理人
     * @param taskId：         任务id params: 流程连接线上的变量 【例】假如流程线上面有一个变量名 ${audit=='通过'}
     *                        则调用时候：params.put("audit","通过"); 这里执行时，会判断控制线的变量 从而控制流程走向
     */
    public static boolean completeTask(String taskAssignName, String taskId, Map<String, Object> params) {
        try {
            Task task = taskService.createTaskQuery().taskId(taskId).taskAssignee(taskAssignName).singleResult();
            if (task == null) {
                // 说明根据任务id和人名 无法查到待办的指定任务
                return false;
            }
            taskService.complete(taskId, params);
            return true;
        } catch (ActivitiException ee) {
            ee.printStackTrace();
            //流程线上已经绑定变量了
            return false;
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("【error】 执行报错： " + e.getMessage());
            return false;
        }
    }

    /**
     * 完成个人任务
     *
     * @param taskAssignName： 任务代理人
     * @param taskId：         任务id params: 流程连接线上的变量 【例】假如流程线上面有一个变量名 ${audit=='通过'}
     *                        则调用时候：params.put("audit","通过"); 这里执行时，会判断控制线的变量 从而控制流程走向
     */
    public static boolean completeTask(String taskAssignName, String taskId) {
        try {
            Task task = taskService.createTaskQuery().taskId(taskId).taskAssignee(taskAssignName).singleResult();
            if (task == null) {
                // 说明根据任务id和人名 无法查到待办的指定任务
                return false;
            }
            taskService.complete(taskId);
            return true;
        } catch (ActivitiException ee) {
            ee.printStackTrace();
            //流程线上已经绑定变量了
            return false;
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("【error】 执行报错： " + e.getMessage());
            return false;
        }
    }

    /**
     * 先执行拾取任务，再进行完成任务。
     *
     * @author zh
     */
    public static boolean claimTaskAndCompleteTask(
            String taskId, String candidateUserName, Map<String, Object> params) {
        if (claimTask(taskId, candidateUserName)) {
            completeTask(candidateUserName, taskId, params);
            return true;
        }
        return false;
    }

    /**
     * 先执行拾取任务，再进行完成任务。
     *
     * @author zh
     */
    public static boolean claimTaskAndCompleteTask(
            String taskId, String candidateUserName) {
        if (claimTask(taskId, candidateUserName)) {
            completeTask(candidateUserName, taskId);
            return true;
        }
        return false;
    }

    /**
     * 根据相应的条件 查出历史的任务信息
     *
     * @param processDefinitionId： 流程定义id
     * @param processInstanceId：   流程实例id
     * @param assigneeName：        代理人名
     * @author zh
     */
    public static List<HistoricActivityInstance> historicActivityInstanceList(
            String processDefinitionId, String processInstanceId, String assigneeName) {
        // 例子：查询 act_hi_actinst 表中数据
        HistoricActivityInstanceQuery instanceQuery =
                historyService.createHistoricActivityInstanceQuery();
        if (processDefinitionId != null) {
            // 查询条件： 根据流程定义id 拿到数据
            instanceQuery.processDefinitionId(processDefinitionId);
        }
        if (processInstanceId != null) {
            // 查询条件： 根据流程实例id 拿到数据
            instanceQuery.processInstanceId(processInstanceId);
        }
        if (assigneeName != null) {
            instanceQuery.taskAssignee(assigneeName);
        }
        return instanceQuery.list();
    }

    /**
     * 通过流程定义id --》 [挂起【暂停】] 或 [激活【启动】] 全部流程实例的执行
     *
     * @author zh
     */
    public static void suspendAllProcessInstance(String key) {
        // 通过指定的key 获取单个流程定义
        ProcessDefinition processDefinition =
                repositoryService.createProcessDefinitionQuery().processDefinitionKey(key).singleResult();
        // 判断指定的流程定义 是否挂起 true=挂起  false=激活
        boolean suspended = processDefinition.isSuspended();
        // 拿到流程定义id
        String processDefinitionId = processDefinition.getId();
        if (suspended) {
            // 说明原来是挂起的 则可以对流程定义和旗下的所有流程实例进行激活操作
            //    参数说明：processDefinitionId=流程定义id 、 activateProcessInstances=挂起或者激活、activationDate=执行事件
            repositoryService.activateProcessDefinitionById(processDefinitionId, true, null);
            System.out.println("对流程定义id为" + processDefinitionId + ",进行[激活]操作");
        } else {
            // 说明原来是激活的 则可以对流程定义和旗下的所有流程实例进行挂起操作
            repositoryService.activateProcessDefinitionById(processDefinitionId, false, null);
            System.out.println("对流程定义id为" + processDefinitionId + ",进行[挂起]操作");
        }
        System.out.println("流程定义" + processDefinition.getName() + "已经操作成功【包括关联的所有实例】");
    }

    /**
     * 针对某一个流程实例进行【挂起】或【激活】操作
     *
     * @author zh
     */
    public static void suspendProcessInstanceByProcessInstanceId(String processInstanceId) {
        ProcessInstance instance =
                runtimeService
                        .createProcessInstanceQuery()
                        .processInstanceId(processInstanceId)
                        .singleResult();
        // 原来是挂起的
        if (instance.isSuspended()) {
            runtimeService.activateProcessInstanceById(instance.getId());
            System.out.println("流程实例id=" + instance.getId() + "，已激活");
        } else {
            runtimeService.suspendProcessInstanceById(instance.getId());
            System.out.println("流程实例id=" + instance.getId() + "，已挂起");
        }
        System.out.println("单个流程实例：" + instance.getName() + "，已经操作成功");
    }


    /**
     * 查询已结束的流程实例
     *
     * @param req
     * @return
     */
    public void getProcInstFinish() {

        HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery()
                .finished() //已结束
                .orderByProcessInstanceEndTime()
                .desc();

        List<HistoricProcessInstance> list =
                query.list();
        list.stream().forEach(l -> {
            System.out.println(l.getId());//流程实例id
            System.out.println(l.getName());//流程名称
            System.out.println(l.getProcessDefinitionKey());//流程定义key
            System.out.println(l.getProcessDefinitionVersion());//流程定义版本
            System.out.println(l.getStartUserId());//流程发起人
            System.out.println(l.getBusinessKey());//业务ID
            System.out.println(l.getStartTime());//流程实例开始时间
            System.out.println(l.getEndTime());//流程实例结束时间
            System.out.println(l.getDeleteReason());//删除原因
        });
    }
}
